package com.cll.prototype.gateway.dto;

import java.util.Arrays;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 描述信息:
 * 方法与每隔时间窗口的对应关系
 * @author CLL
 * @version 1.0
 * @date 2020/11/9 13:50
 */
public class MethodIpPercent {

    /***
     * 计算的时间窗口
     */
    private final int calculateTimeWindow;

    /**
     * 最高请求次数
     */
    private final int maxTimes;

    /**
     * 每秒中请求信息
     * 当前时间窗口内按照秒为单位进行请求的统计信息
     */
    private final MethodTopPercentileSecond[] topPercentileSeconds;

    public static MethodIpPercent getInstance(int calculateTimeWindow, int times){
        return new MethodIpPercent(calculateTimeWindow, times);
    }

    /**
     * 记录请求的信息
     * @param invokeMethod  执行方法
     * @param useTime   耗时
     */
    public void increment(String invokeMethod, long useTime){
        // 当前时间
        final long currentTimeMillis = System.currentTimeMillis();
        // 计算当前时间，在时间窗口中的位置。假如统计的时间窗口是5s，
        // 当前系统时间对事件窗口取余，计算在时间窗口的第二秒，那么当前请求就是在时间窗口的第二秒中进行记录
        final int idx = (int) (currentTimeMillis / 1000 % calculateTimeWindow);
        this.topPercentileSeconds[idx].increment(invokeMethod, useTime, currentTimeMillis);
    }

    /**
     * 检测当前请求是否被限制
     * @param methodName    请求的方法的名称
     * @return  true:被限制;false:未被限制
     */
    public boolean isLimited(String methodName){
        AtomicLong totalCount = new AtomicLong(0);
        Arrays.stream(topPercentileSeconds).forEach((topPercentileSecond) -> {
            // 将当前
            totalCount.addAndGet(topPercentileSecond.count(this.calculateTimeWindow));
        });
        return totalCount.get() >= maxTimes;
    }


    /**
     * 私有构造方法
     * @param calculateTimeWindow   时间窗口
     * @param maxTimes  限制的做高的请求次数
     */
    private MethodIpPercent(int calculateTimeWindow, int maxTimes) {
        this.calculateTimeWindow = calculateTimeWindow;
        this.maxTimes = maxTimes;
        this.topPercentileSeconds = new MethodTopPercentileSecond[calculateTimeWindow];
        // 给当前时间窗口内的每个单元进行请求信息的初始化
        for (int i = 0; i < calculateTimeWindow; i++) {
            this.topPercentileSeconds[i] = new MethodTopPercentileSecond();
        }
    }

    /**
     * 封装了方法的请求TP 90、TP 99等的对象
     * 根据方法的执行情况，获取方法的TP90、TP99等
     * 每秒中的数据信息
     */
    private static class MethodTopPercentileSecond {

        /**
         * 当前这一秒时间窗口下的执行情况
         */
        private volatile AtomicLong currentSecondTimes = new AtomicLong(0);

        /**
         * 最后一次执行方法递增的时间信息。毫秒
         */
        private volatile long lastIncrementTime = -1;

        /**
         * 方法执行一次之后，进行记录
         * @param invokeMethod  执行方法
         * @param useTime   耗时
         * @param currentMillis 当前时间
         */
        public void increment(String invokeMethod, long useTime, long currentMillis){
            // 1 s = 1000 ms,当执行时间超过当前时间窗口的有效期（1s）之后，进行时间窗口内数据的刷新
            if (lastIncrementTime > 0 && currentMillis - lastIncrementTime > 1000) {
                synchronized (this){
                    if (currentMillis - lastIncrementTime > 1000) {
                        // 刷新当前时间窗口的执行次数
                        currentSecondTimes.set(0);
                        lastIncrementTime = currentMillis;
                    }
                }
            }

            currentSecondTimes.incrementAndGet();
            lastIncrementTime = currentMillis;
        }

        /**
         * 计算时间窗口内，执行的次数
         * @param timeWindow    时间窗口
         * @return  获取执行时间窗口内
         */
        public long count(int timeWindow){
            // 记录的
            final long currentTimeMillis = System.currentTimeMillis() - (timeWindow * 1000);
            if (lastIncrementTime > currentTimeMillis) {
                return currentSecondTimes.get();
            }
            return 0L;
        }
    }
}